package com.ruoyi.framework.security.filter;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.annotation.ApiSignature;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.ApiSignatureException;
import com.ruoyi.common.utils.ApiSignatureUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.spring.SpringUtils;

/**
 * API签名认证过滤器
 *
 * @author ruoyi
 */
//@Component
public class ApiSignatureFilter extends OncePerRequestFilter {
    // 签名参数名
    private static final String SIGN_PARAM = "sign";

    // 时间戳参数名
    private static final String TIMESTAMP_PARAM = "timestamp";

    // 随机字符串参数名
    private static final String NONCE_PARAM = "nonce";

    //userId参数名
    private static final String USER_ID = "userId";


    // 签名方法参数名
    private static final String SIGN_METHOD_PARAM = "sign_method";

    // 默认签名方法
    private static final String DEFAULT_SIGN_METHOD = "HmacSHA256";

    // 时间戳有效时间(毫秒)，默认5分钟
    private static final long TIMESTAMP_VALID_TIME = 5 * 60 * 1000;

    // 模拟的API密钥，实际应该从数据库或配置文件中获取
    private static final String SECRET_KEY = "ruoyi-api-secret-key";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        try {
            // 检查是否需要签名认证
            if (isSignatureRequired(request)) {
                // 验证签名
                validateSignature(request);
            }
        } catch (ApiSignatureException e) {
            // 签名验证失败，返回错误信息
            AjaxResult result = AjaxResult.error(401, e.getMessage());
            ServletUtils.renderString(response, JSON.toJSONString(result));
            return;
        }

        chain.doFilter(request, response);
    }

    /**
     * 判断接口是否需要签名认证
     *
     * @param request 请求
     * @return 是否需要签名认证
     */
    private boolean isSignatureRequired(HttpServletRequest request) {
        try {
            RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping");
            HandlerExecutionChain handler = handlerMapping.getHandler(request);
            if (handler != null && handler.getHandler() instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler();
                // 检查方法上是否有@ApiSignature注解
                ApiSignature apiSignature = handlerMethod.getMethodAnnotation(ApiSignature.class);
                if (apiSignature != null) {
                    return apiSignature.required();
                }

                // 检查类上是否有@ApiSignature注解
                apiSignature = handlerMethod.getBeanType().getAnnotation(ApiSignature.class);
                if (apiSignature != null) {
                    return apiSignature.required();
                }
            }
        } catch (Exception e) {
            // 出现异常，默认不需要签名认证
            return false;
        }

        return false;
    }

    /**
     * 验证签名
     *
     * @param request 请求
     */
    public void validateSignature(HttpServletRequest request) {
        // 获取签名参数
        String sign = request.getHeader(SIGN_PARAM);
        String timestamp = request.getHeader(TIMESTAMP_PARAM);
        String nonce = request.getHeader(NONCE_PARAM);
        String userId = request.getHeader(USER_ID);

        // 检查必要参数
        if (StringUtils.isEmpty(sign)) {
            throw new ApiSignatureException("缺少签名参数");
        }

        if (StringUtils.isEmpty(timestamp)) {
            throw new ApiSignatureException("缺少时间戳参数");
        }
        RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
        String nonceKey = NONCE_PARAM + ":" + nonce;
        if (StringUtils.isEmpty(nonce)) {
            throw new ApiSignatureException("缺少随机字符串参数");
        } else {
            Boolean existsKey = redisCache.hasKey(nonceKey);
            if (existsKey) {
                throw new ApiSignatureException("不支持的重放请求");
            }

        }


        if (StringUtils.isEmpty(userId)) {
            throw new ApiSignatureException("缺少用户编号参数");
        }

        //TODO   验证时间戳
//        if (!validateTimestamp(timestamp))
//        {
//            throw new ApiSignatureException("时间戳已过期");
//        }


        // 获取所有请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        Map<String, String> signParams = ApiSignatureUtils.getSignParams(parameterMap);


        //psot  json请求
        if ("POST".equalsIgnoreCase(request.getMethod())) {
            String contentType = request.getContentType();
            if (contentType != null && contentType.contains("application/json")) {
                if (request instanceof RepeatedlyRequestWrapper) {
                    RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
                    String nowParams = HttpHelper.getBodyString(repeatedlyRequest);

                    Map map = JSON.parseObject(nowParams, Map.class);
                    signParams = new HashMap<>(map);
                }

            }

        }


        // 移除签名参数，签名参数不参与签名
        signParams.remove(SIGN_PARAM);
        // 验证签名
        boolean isValid = ApiSignatureUtils.verifySignature(signParams, userId, sign);
        if (!isValid) {
            throw new ApiSignatureException("签名验证失败");
        }
        //验证成功 设置重放 过期时间 当前时间+4分中 减10秒
        LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(4);
        int second = localDateTime.getSecond();
        redisCache.setCacheObject(nonceKey, nonce, second - 10, TimeUnit.SECONDS);

    }

    /**
     * 验证时间戳
     *
     * @param timestamp 时间戳字符串
     * @return 是否有效
     */
    private boolean validateTimestamp(String timestamp) {
        try {
            long requestTime = Long.parseLong(timestamp);
            long currentTime = System.currentTimeMillis();
            // 判断时间戳是否在有效期内
            return Math.abs(currentTime - requestTime) <= TIMESTAMP_VALID_TIME;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}